import { Action2, Action3 } from "../../../Support/Share/Lib/Action";
import { ConcurrentQueue } from "../../../Support/Share/Lib/Collections/ConcurrentQueue";
import { Queue } from "../../../Support/Share/Lib/Collections/Queue";
import { Dictionary } from "../../../Support/Share/Lib/Dictionary";
import { DoubleMap } from "../../../Support/Share/Lib/DoubleMap";
import { GetCustomAttributes, GetTypeOf, MessageAttrData, MessageAttribute } from "../../../Decorator/Decorator";
import { Singleton } from "../../Singleton/Singleton";
import { EventSystem } from "../EventSystem/EventSystem";
import { Log } from "../Log/Log";
import { AService } from "./AService";
import { ETTask } from "../../../Support/Share/Lib/Async/ETTask";

export enum NetworkProtocol {
    KCP,
    Websocket,
}

export enum NetOp {
    AddService = 1,
    RemoveService = 2,
    OnAccept = 3,
    OnRead = 4,
    OnError = 5,
    CreateChannel = 6,
    RemoveChannel = 7,
    SendMessage = 9,
}

export class NetOperator {
    public Op: NetOp; // 操作码
    public ServiceId: number;
    public ChannelId: number;
    public ActorId: number;
    public Object; // 参数
    public tcs: ETTask<void>;
}

export class NetServices extends Singleton<NetServices> {

    private static _Instance: NetServices;
    static get Instance() {
        return this._Instance;
    }

    protected set Instance(value: NetServices) {
        NetServices._Instance = value;
    }

    private readonly netThreadOperators = new ConcurrentQueue<NetOperator>();
    private readonly mainThreadOperators = new ConcurrentQueue<NetOperator>();

    constructor() {
        super();
        let types = EventSystem.Instance.GetTypes(GetTypeOf(MessageAttribute));
        for (let type in types) {
            let clazz = types[type];
            let attrs = GetCustomAttributes(clazz);
            if (attrs.length == 0) {
                continue;
            }
            let messageAttribute = attrs[0] as MessageAttrData;
            if (messageAttribute == null) {
                continue;
            }
            this.typeOpcode.Add(GetTypeOf(clazz), messageAttribute.Opcode);
        }
    }

    private readonly typeOpcode = new DoubleMap<string, number>();

    public GetOpcode(type: string) {
        return this.typeOpcode.GetValueByKey(type);
    }

    public GetType(opcode: number) {
        return this.typeOpcode.GetKeyByValue(opcode);
    }

    private readonly acceptCallback = new Dictionary<number, Action2<number, number>>();
    private readonly readCallback = new Dictionary<number, Action3<number, number, Object>>();
    private readonly errorCallback = new Dictionary<number, Action2<number, number>>();

    private serviceIdGenerator: number = 0;

    public SendMessage(serviceId: number, channelId: number, actorId: number, message) {
        let netOperator = new NetOperator();
        {
            netOperator.Op = NetOp.SendMessage;
            netOperator.ServiceId = serviceId;
            netOperator.ChannelId = channelId;
            netOperator.ActorId = actorId;
            netOperator.Object = message;
        }
        this.netThreadOperators.Enqueue(netOperator);
    }

    public AddService(aService: AService) {
        aService.Id = ++this.serviceIdGenerator;
        let netOperator = new NetOperator();
        {
            netOperator.Op = NetOp.AddService;
            netOperator.ServiceId = aService.Id;
            netOperator.ChannelId = 0;
            netOperator.Object = aService;
        }
        this.netThreadOperators.Enqueue(netOperator);
        return aService.Id;
    }

    public RemoveService(serviceId: number) {
        let netOperator = new NetOperator();
        {
            netOperator.Op = NetOp.RemoveService;
            netOperator.ServiceId = serviceId;
        }
        this.netThreadOperators.Enqueue(netOperator);
    }

    public RemoveChannel(serviceId: number, channelId: number, error: number) {
        let netOperator = new NetOperator();
        {
            netOperator.Op = NetOp.RemoveChannel;
            netOperator.ServiceId = serviceId;
            netOperator.ChannelId = channelId;
            netOperator.ActorId = error;
        }
        this.netThreadOperators.Enqueue(netOperator);
    }

    public async CreateChannel(serviceId: number, channelId: number, address: string) {
        let tcs = ETTask.Create<void>();
        let netOperator = new NetOperator();
        {
            netOperator.Op = NetOp.CreateChannel;
            netOperator.ServiceId = serviceId;
            netOperator.ChannelId = channelId;
            netOperator.Object = address;
            netOperator.tcs = tcs;
        }
        this.netThreadOperators.Enqueue(netOperator);
        return tcs;
    }

    public RegisterAcceptCallback(serviceId: number, action) {
        this.acceptCallback.Add(serviceId, action);
    }

    public RegisterReadCallback(serviceId: number, action) {
        this.readCallback.Add(serviceId, action);
    }

    public RegisterErrorCallback(serviceId: number, action) {
        this.errorCallback.Add(serviceId, action);
    }

    public UpdateInMainThread() {
        while (true) {
            let op = this.mainThreadOperators.TryDequeue();
            if (!op) {
                return;
            }

            try {
                switch (op.Op) {
                    case NetOp.OnAccept:
                        {
                            let action = this.acceptCallback.TryGetValue(op.ServiceId);
                            if (!action) {
                                return;
                            }
                            action.call(null, op.ChannelId, op.Object);
                            break;
                        }
                    case NetOp.OnRead:
                        {
                            let action = this.readCallback.TryGetValue(op.ServiceId);
                            if (!action) {
                                return;
                            }
                            action.call(null, op.ChannelId, op.ActorId, op.Object);
                            break;
                        }
                    case NetOp.OnError:
                        {
                            let action = this.errorCallback.TryGetValue(op.ServiceId);
                            if (!action) {
                                return;
                            }

                            action.call(null, op.ChannelId, op.ActorId);
                            break;
                        }
                    default:
                        throw new Error(`not found net operator: ${op.Op}`);
                }
            }
            catch (e) {
                Log.Error(e);
            }
        }
    } Í

    private readonly services = new Dictionary<number, AService>();
    private readonly queue = new Queue<number>();

    private Add(aService: AService) {
        this.services.Add(aService.Id, aService);
        this.queue.Enqueue(aService.Id);

        // console.log("NetServices.add");
        // console.log(this.queue);
    }

    public Get(id): AService {
        let aService = this.services.TryGetValue(id);
        return aService;
    }

    private Remove(id: number) {
        let service: AService;
        if (this.services.Remove(id, service)) {
            service.Dispose();
        }
    }

    private async RunNetThreadOperator() {
        while (true) {
            let op = this.netThreadOperators.TryDequeue();
            if (!op) {
                return;
            }

            try {
                switch (op.Op) {
                    case NetOp.AddService:
                        {
                            this.Add(op.Object as AService);
                            break;
                        }
                    case NetOp.RemoveService:
                        {
                            this.Remove(op.ServiceId);
                            break;
                        }
                    case NetOp.CreateChannel:
                        {
                            let tcs = op.tcs;
                            try {
                                let service = this.Get(op.ServiceId);
                                if (service != null) {
                                    await service.Create(op.ChannelId, op.Object);
                                }
                                tcs.SetResult();
                            } catch (e) {
                                tcs.SetException(e);
                            }
                            break;
                        }
                    case NetOp.RemoveChannel:
                        {
                            let service = this.Get(op.ServiceId);
                            if (service != null) {
                                service.Remove(op.ChannelId, op.ActorId);
                            }
                            break;
                        }
                    case NetOp.SendMessage:
                        {
                            let service = this.Get(op.ServiceId);
                            if (service != null) {
                                service.Send(op.ChannelId, op.ActorId, op.Object);
                            }
                            break;
                        }
                    default:
                        throw new Error(`not found net operator: ${op.Op}`);
                }
            }
            catch (e) {
                Log.Error(e);
            }
        }
    }

    public UpdateInNetThread() {
        let count = this.queue.Count;
        while (count-- > 0) {
            let serviceId = this.queue.Dequeue();
            let service = this.services.TryGetValue(serviceId);
            if (!service) {
                continue;
            }
            this.queue.Enqueue(serviceId);
            service.Update();
        }

        this.RunNetThreadOperator();
    }

    public OnAccept(serviceId: number, channelId: number, ipEndPoint: number) {
        let netOperator = new NetOperator();
        {
            netOperator.Op = NetOp.OnAccept;
            netOperator.ServiceId = serviceId;
            netOperator.ChannelId = channelId;
            netOperator.Object = ipEndPoint;
        }
        this.mainThreadOperators.Enqueue(netOperator);
    }

    public OnRead(serviceId: number, channelId: number, actorId: number, message) {
        let netOperator = new NetOperator();
        {
            netOperator.Op = NetOp.OnRead;
            netOperator.ServiceId = serviceId;
            netOperator.ChannelId = channelId;
            netOperator.ActorId = actorId;
            netOperator.Object = message;
        }
        this.mainThreadOperators.Enqueue(netOperator);
    }

    public OnError(serviceId: number, channelId: number, error: number) {
        let netOperator = new NetOperator()
        {
            netOperator.Op = NetOp.OnError;
            netOperator.ServiceId = serviceId;
            netOperator.ChannelId = channelId;
            netOperator.ActorId = error;
        }
        this.mainThreadOperators.Enqueue(netOperator);
    }

    private connectIdGenerator = 0;
    public CreateConnectChannelId(): number {
        return ++this.connectIdGenerator;
    }

    private acceptIdGenerator = Number.MAX_SAFE_INTEGER;
    public CreateAcceptChannelId() {
        return --this.acceptIdGenerator;
    }

    public Dispose(): void {

    }

}